const PouchDB = require('pouchdb')
const PouchDBFind = require('pouchdb-find')
const path = require('node:path')

PouchDB.plugin(PouchDBFind)

export const PouchDBType_Name = 'PouchDBType'
export const PouchDBConfOptionsType_Name = 'PouchDBConfOptionsType'
export const PouchDBSaveReturnType_Name = 'PouchDBSaveReturnType'
export const PouchDBSaveBatchReturnType_Name = 'PouchDBSaveBatchReturnType'
export const PouchDBFindOptionsType_Name = 'PouchDBFindOptionsType'
export const PouchDBFindReturnType_Name = 'PouchDBFindReturnType'
export const PouchDBRemoveReturnType_Name = 'PouchDBRemoveReturnType'
export const PouchDBRemoveBatchReturnType_Name = 'PouchDBRemoveBatchReturnType'
export const PouchDBDestroyReturnType_Name = 'PouchDBDestroyReturnType'
export const DB_Name = 'DB'

export type PouchDBType = PouchDB.Database<Record<string, any>>

export type PouchDBConfOptionsType = PouchDB.Configuration.DatabaseConfiguration

export type PouchDBSaveReturnType = Promise<PouchDB.Core.Response>

export type PouchDBSaveBatchReturnType = Promise<Array<PouchDB.Core.Response | PouchDB.Core.Error>>

export type PouchDBFindOptionsType = Partial<PouchDB.Find.FindRequest<Record<string, any>>>

export type PouchDBFindReturnType = Promise<PouchDB.Find.FindResponse<Record<string, any>>>

export type PouchDBRemoveReturnType = Promise<PouchDB.Core.Response>

export type PouchDBRemoveBatchReturnType = Promise<
  Array<PouchDB.Core.Response | PouchDB.Core.Error>
>

export type PouchDBDestroyReturnType = Promise<void>

export class DB {
  private db: PouchDBType

  /**
   * 创建用于保存和操作 PouchDB 数据库的对象
   *
   * @param {string} dbName 数据库名称
   * @param {string} dbPath 数据库存放路径，即数据库存放在哪个目录下，默认为当前执行脚本所
   * 在的目录 `__dirname`
   * @param {PouchDBConfOptionsType} options 数据库配置选项，
   * 数据库底层存储引擎默认使用 leveldb
   */
  constructor(dbName: string, dbPath: string = __dirname, options: PouchDBConfOptionsType = {}) {
    this.db = new PouchDB(path.join(dbPath, dbName), {
      adapter: 'leveldb',
      ...options
    })
  }

  /**
   * 新增数据到数据库
   *
   * @param {Record<string, any>} doc 需要新增的数据
   * @returns {PouchDBSaveReturnType} 新增结果
   */
  async add(doc: Record<string, any>): PouchDBSaveReturnType {
    return await this.db.post(doc)
  }

  /**
   * 更新数据库中的数据
   *
   * @param {Record<string, any>} doc 需要更新到数据库中的数据，数据中需要包含 `_id` 字段，且根据 `_id` 字段的值从数据库中能够查询到数据
   * @returns {PouchDBSaveReturnType} 更新结果
   */
  async update(doc: Record<string, any>): PouchDBSaveReturnType {
    const oldDoc = await this.db.get(doc._id)
    for (const key in doc) {
      if (key === '_id' || key === '_rev') continue
      oldDoc[key] = doc[key]
    }
    return await this.db.put(oldDoc)
  }

  /**
   * 保存数据到数据库
   *
   * @param {Record<string, any>} doc 需要保存的数据，如果数据中包含 `_id` 字段，且根据
   * `_id` 字段的值从数据库中能够查询到数据，则更新数据，否则向数据库中新增数据
   * @returns {PouchDBSaveReturnType} 保存结果
   */
  async save(doc: Record<string, any>): PouchDBSaveReturnType {
    if (Reflect.has(doc, '_id')) return await this.update(doc)
    else return await this.add(doc)
  }

  /**
   * 批量保存数据到数据库
   *
   * @param {Record<string, any>[]} docs 需要保存的数据
   * @returns {PouchDBSaveBatchReturnType} 保存结果
   */
  async saveBatch(docs: Record<string, any>[]): PouchDBSaveBatchReturnType {
    for (const doc of docs) {
      if (Reflect.has(doc, '_id')) {
        // 确保 _rev 未过期
        const oldDoc = await this.db.get(doc._id)
        doc._rev = oldDoc._rev
      }
    }
    return await this.db.bulkDocs(docs)
  }

  /**
   * 从数据库中查询数据
   *
   * @param {PouchDBFindOptionsType} options 查询配置选项
   * @returns {PouchDBFindReturnType} 查询结果
   */
  find(options: PouchDBFindOptionsType = {}): PouchDBFindReturnType {
    return this.db.find({
      selector: {},
      ...options
    })
  }

  /**
   * 根据 `_id` 删除数据
   *
   * @param {string} id 需要删除的数据的 `_id` 字段的值
   * @returns {PouchDBRemoveReturnType} 删除结果
   */
  async removeById(id: string): PouchDBRemoveReturnType {
    const doc = await this.db.get(id)
    return await this.db.remove(doc)
  }

  /**
   * 根据 `_id` 批量删除数据
   *
   * @param {string[]} ids 需要删除的数据的 `_id` 字段的值组成的列表
   * @returns {PouchDBRemoveBatchReturnType} 删除结果
   */
  async removeByIds(ids: string[]): PouchDBRemoveBatchReturnType {
    const docsRes = await this.db.allDocs({ keys: ids, include_docs: true })
    const docsToRemove: Record<string, any>[] = []
    for (const row of docsRes.rows) {
      if (!Reflect.has(row, 'doc')) continue
      docsToRemove.push({
        ...(Reflect.get(row, 'doc') as Record<string, any>),
        _deleted: true
      })
    }
    return await this.db.bulkDocs(docsToRemove)
  }

  /**
   * 根据条件删除数据
   *
   * @param {PouchDBFindOptionsType} options 要删除数据的查询配置选项
   * @returns {PouchDBRemoveBatchReturnType} 删除结果
   */
  async remove(options: PouchDBFindOptionsType = {}): PouchDBRemoveBatchReturnType {
    const docsRes = await this.find(options)
    const docsToRemove = docsRes.docs.map(doc => ({ ...doc, _deleted: true }))
    return await this.db.bulkDocs(docsToRemove)
  }

  /**
   * 销毁数据库
   *
   * @returns {PouchDBDestroyReturnType} 销毁结果
   */
  async destory(): PouchDBDestroyReturnType {
    return await this.db.destroy()
  }
}
